import { newArrayObj } from "./array";
import Dep from "./dep";

/* 响应式模块 */
class Observe {
  constructor(data) {
    // 给每一个对象都增加收集功能
    // 给数组本身增加dep,如果数组新增了某一项我可以触发dep更新
    // 给对象也增加dep,如果对象增添了属性我可以触发dep更新
    // > 新增属性要用$set才能更新视图
    this.dep = new Dep();

    /* 
        Object.defineProperty只能劫持已经存在的属性，后增的，或者删除的 ，不能劫持到
        > Vue2里面会为此单独写一些api $set $delete
    */

    //   给数据加了一个Observe实例的标识，如果数据上有__ob__,则说明数据被观测过了
    // data.__ob__ = this; // this是Observe实例（不能这样写，因为__ob__也是一个对象，会重复循环劫持）
    Object.defineProperty(data, "__ob__", {
      value: this,
      enumerable: false, // 定义为不可枚举（循环的时候无法获取）
    });

    if (Array.isArray(data)) {
      // 添加实例做劫持的过程中，对数组做特殊处理
      // > 1.重写数组中的方法 7个变异方法，可以修改数组本身
      //     eg.vm.hobby.push(1)
      // 保留数组原有的特性，并且可以重写部分方法
      data.__proto__ = newArrayObj;

      // > 2.而且也需要对数组里面的引用类型做劫持
      //     eg.vm.hobby[2].a = 100
      this.observeArray(data);
    } else {
      this.walk(data);
    }
  }

  // 循环对象 对属性依次进行劫持
  walk(data) {
    // “重新定义属性” （性能差）
    Object.keys(data).forEach((key) => {
      // > 将属性变成响应式的
      defineReactive(data, key, data[key]);
    });
  }

  // 观测数组 对数组里面的引用类型做劫持
  observeArray(data) {
    // 对对象进行劫持
    data.forEach((item) => observe(item));
  }
}

// 深层次嵌套会递归，递归多了性能差，不存在属性监控不到，存在的属性要重写方法
function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
    const current = value[i];
    current.__ob__?.dep.depend();
    if (Array.isArray(current)) {
      dependArray(current);
    }
  }
}

/* 将属性变成响应式的 (闭包)*/
export function defineReactive(target, key, value) {
  // 如果值是一个对象，再对对象进行劫持
  let childOb = observe(value); // childOb.dep 用来收集依赖

  // 这个就是使用了闭包,所以dep实例才没有被销毁
  let dep = new Dep(); //每一个属性都有一个dep

  Object.defineProperty(target, key, {
    get() {
      // 取值调用get
      if (Dep.target) {
        dep.depend(); // 让这个属性收集器记住当前的watcher

        if (childOb) {
          childOb.dep.depend(); //数组和对象本身也实现依赖收集

          // 数组里面是否有嵌套,有递归依赖收集
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }

      return value;
    },
    set(newValue) {
      // 改值调用set
      if (value === newValue) return;
      value = newValue;

      dep.notify(); // 通知更新
    },
  });
}

/* 对这个对象进行劫持 */
export function observe(data) {
  /* 
    只对对象进行劫持
    注意：数组也是typeof也是对象，如果对数组添加实例化进行劫持，会将数组每一项做劫持
    但是修改数组很少用索引来操作数组（[9999]），内部每一项都做劫特会浪费性能，
    用户修改数组一般都是通过方法来修改 push shift... 
    > 添加实例做劫持的过程中，对数组做特殊处理
  */
  if (typeof data !== "object" || data == null) {
    return;
  }

  // 说明对象被 检测过了
  if (data.__ob__ instanceof Observe) {
    return data.__ob__;
  }

  /* 
    通过new Observe类来对对象进行观测劫持
    并通过给数据加了一个Observe实例的标识，来判断数据是否被观测过了

    如果一个对象被劫持过了， 那就不需要再被劫持了
  */
  return new Observe(data);
}
